package com.tsfyun.scm.service.impl.customs;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.tsfyun.common.base.config.OpenApiAccountProperties;
import com.tsfyun.common.base.dto.DeclareBizSendDTO;
import com.tsfyun.common.base.dto.Result;
import com.tsfyun.common.base.dto.TaskDTO;
import com.tsfyun.common.base.enums.*;
import com.tsfyun.common.base.enums.customs.TrafModeCodeEnum;
import com.tsfyun.common.base.enums.domain.*;
import com.tsfyun.common.base.enums.singlewindow.SingleWindowDeclarationStatusEnum;
import com.tsfyun.common.base.enums.singlewindow.SingleWindowLogEnum;
import com.tsfyun.common.base.exception.ServiceException;
import com.tsfyun.common.base.help.excel.ExcelUtil;
import com.tsfyun.common.base.security.OpenApiAccount;
import com.tsfyun.common.base.security.SecurityUtil;
import com.tsfyun.common.base.support.DomainStatus;
import com.tsfyun.common.base.util.*;
import com.tsfyun.common.base.validator.ValidatorUtils;
import com.tsfyun.scm.base.DataCalling;
import com.tsfyun.scm.config.declare.DeclareNoticeSender;
import com.tsfyun.scm.config.declare.ProducerRabbitConfig;
import com.tsfyun.scm.dto.customs.ConfirmDeclarationDTO;
import com.tsfyun.scm.dto.customs.DeclarationQTO;
import com.tsfyun.scm.dto.customs.WaybillBindOrderUpdateDecDTO;
import com.tsfyun.scm.dto.support.TaskNoticeContentDTO;
import com.tsfyun.scm.entity.base.Currency;
import com.tsfyun.scm.entity.customer.Customer;
import com.tsfyun.scm.entity.customs.Declaration;
import com.tsfyun.scm.entity.customs.DeclarationMember;
import com.tsfyun.scm.entity.customs.DeclarationTemp;
import com.tsfyun.scm.entity.customs.LogSingleWindow;
import com.tsfyun.scm.entity.logistics.CrossBorderWaybill;
import com.tsfyun.scm.entity.order.ExpOrder;
import com.tsfyun.scm.entity.order.ImpOrder;
import com.tsfyun.scm.entity.system.Subject;
import com.tsfyun.scm.entity.system.SubjectOverseas;
import com.tsfyun.scm.excel.ImportDeclarationNoExcel;
import com.tsfyun.scm.mapper.customs.DeclarationMapper;
import com.tsfyun.scm.service.base.ICurrencyService;
import com.tsfyun.scm.service.base.ISystemCacheService;
import com.tsfyun.scm.service.common.ICommonService;
import com.tsfyun.scm.service.customer.ICustomerService;
import com.tsfyun.scm.service.customer.ISupplierService;
import com.tsfyun.scm.service.customs.IDeclarationMemberService;
import com.tsfyun.scm.service.customs.IDeclarationService;
import com.tsfyun.common.base.extension.ServiceImpl;
import com.tsfyun.scm.service.customs.IDeclarationTempService;
import com.tsfyun.scm.service.customs.ILogSingleWindowService;
import com.tsfyun.scm.service.file.IUploadFileService;
import com.tsfyun.scm.service.logistics.ICrossBorderWaybillService;
import com.tsfyun.scm.service.logistics.IDeliveryWaybillService;
import com.tsfyun.scm.service.materiel.IMaterielBrandService;
import com.tsfyun.scm.service.order.IExpOrderMemberService;
import com.tsfyun.scm.service.order.IExpOrderService;
import com.tsfyun.scm.service.order.IImpOrderMemberService;
import com.tsfyun.scm.service.order.IImpOrderService;
import com.tsfyun.scm.service.support.ITaskNoticeContentService;
import com.tsfyun.scm.service.system.IStatusHistoryService;
import com.tsfyun.scm.service.system.ISubjectOverseasService;
import com.tsfyun.scm.service.system.ISubjectService;
import com.tsfyun.scm.support.LoginHelper;
import com.tsfyun.scm.support.SubjectBiz;
import com.tsfyun.scm.support.singlewindow.DeclareBiz;
import com.tsfyun.scm.support.singlewindow.ManifestBiz;
import com.tsfyun.scm.system.vo.CustomsCodeVO;
import com.tsfyun.scm.system.vo.CustomsElementsVO;
import com.tsfyun.scm.util.ExportUtil;
import com.tsfyun.scm.util.MaterielUtil;
import com.tsfyun.scm.util.TsfWeekendSqls;
import com.tsfyun.scm.vo.customer.BuyerSellerInfo;
import com.tsfyun.scm.vo.customs.*;
import com.tsfyun.scm.vo.customs.singlewindow.ManifestPlusVO;
import com.tsfyun.scm.vo.order.ExpOrderMemberVO;
import com.tsfyun.scm.vo.order.ImpOrderMemberVO;
import com.tsfyun.scm.vo.system.SubjectVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.LinkedMap;
import org.apache.commons.lang.ArrayUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * <p>
 * 报关单 服务实现类
 * </p>
 *
 *
 * @since 2020-04-24
 */
@RefreshScope
@Slf4j
@Service
public class DeclarationServiceImpl extends ServiceImpl<Declaration> implements IDeclarationService {

    @Autowired
    private DeclarationMapper declarationMapper;
    @Autowired
    private ISystemCacheService systemCacheService;
    @Autowired
    private IDeclarationMemberService declarationMemberService;
    @Autowired
    private IStatusHistoryService statusHistoryService;
    @Autowired
    private RedisLockRegistry redisLockRegistry;
    @Autowired
    private ITaskNoticeContentService taskNoticeContentService;
    @Autowired
    private ICommonService commonService;
    @Value("${redis.lock.time:5}")
    private Integer lockTime;
    @Resource
    private DeclareBiz declareBiz;
    @Autowired
    private ILogSingleWindowService logSingleWindowService;
    @Autowired
    private IImpOrderMemberService impOrderMemberService;
    @Autowired
    private IExpOrderMemberService expOrderMemberService;
    @Autowired
    private ICustomerService customerService;
    @Autowired
    private IDeclarationTempService declarationTempService;
    @Autowired
    private ISubjectService subjectService;
    @Autowired
    private Snowflake snowflake;
    @Autowired
    private DeclareNoticeSender declareNoticeSender;
    @Autowired
    private IImpOrderService impOrderService;
    @Autowired
    private IExpOrderService expOrderService;
    @Autowired
    private IDeliveryWaybillService deliveryWaybillService;
    @Resource
    private ManifestBiz manifestBiz;
    @Resource
    private IMaterielBrandService materielBrandService;
    @Autowired
    private IUploadFileService uploadFileService;
    @Autowired
    private ISubjectOverseasService subjectOverseasService;
    @Autowired
    private ICurrencyService currencyService;
    @Autowired
    private ICrossBorderWaybillService crossBorderWaybillService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private OpenApiAccountProperties openApiAccountProperties;
    @Autowired
    private ISupplierService supplierService;
    @Resource
    private LoginHelper loginHelper;
    @Resource
    private SubjectBiz subjectBiz;
    //发送至报关行
    private static final String sendToScmbotUrl = "https://www.xxxx.com/declare/declaration/openApiSaveDeclaration";
    //获取accessToken
    private static final String accessTokenUrl = "https://www.xxxx.com/scm/auth/getAccessToken";

    @Value("${scmbot.declare.requesturl:''}")
    private String scmDeclareUrl;
    @Value("${scmbot.secret}")
    private String salt;
    @Value("${upload.excel.maxmum:500}")
    private int uploadMaxmum;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Declaration createByImpOrder(ImpOrder order) {
        //获取报关单默认模板
        DeclarationTemp temp = null;
        if(Objects.nonNull(order.getDeclarationTempId())){
            temp = declarationTempService.getById(order.getDeclarationTempId());
        }
        if(Objects.isNull(temp)){
            temp = declarationTempService.obtanDefault(BillTypeEnum.IMP);
        }
        TsfPreconditions.checkArgument(Objects.nonNull(temp),new ServiceException("创建报关单失败：未找到默认进口报关模板，请联系关务维护"));
        Declaration declaration = beanMapper.map(temp,Declaration.class);

        //申报单位信息（外贸合同买方）
        Subject subject = subjectService.getById(order.getSubjectId());
        TsfPreconditions.checkArgument(Objects.nonNull(subject),new ServiceException("申报单位信息不存在"));
        declaration.setAgentName(subject.getName());//申报单位名称
        declaration.setAgentScc(subject.getSocialNo());//统一社会信用代码
        TsfPreconditions.checkArgument((StringUtils.isNotEmpty(declaration.getAgentScc()) && declaration.getAgentScc().length() == 18),new ServiceException(String.format("请先维护【%s】18位社会信用代码",declaration.getAgentName())));
        declaration.setAgentCode(subject.getCustomsCode());//海关注册编码
        TsfPreconditions.checkArgument((StringUtils.isNotEmpty(declaration.getAgentCode()) && declaration.getAgentCode().length() == 10),new ServiceException(String.format("请先维护【%s】10位海关代码",declaration.getAgentName())));
        //境内收发货人（外贸合同买方）
        declaration.setSubjectId(order.getSubjectId());
        //境外收发货人（外贸合同卖方）
        declaration.setSubjectOverseasId(order.getSubjectOverseasId());
        SubjectOverseas subjectOverseas = subjectOverseasService.getById(order.getSubjectOverseasId());
        Optional.ofNullable(subjectOverseas).orElseThrow(()->new ServiceException("境外公司不存在"));
        declaration.setSubjectOverseasType("1");
        declaration.setSubjectOverseasName(subjectOverseas.getName());
        //消费使用单位
        DeclareTypeEnum declareType = DeclareTypeEnum.of(order.getDeclareType());

        if(Objects.equals(DeclareTypeEnum.SINGLE,declareType)){
            //单抬头 消费使用单位 == 境内收发货人
            declaration.setOwnerName(subject.getName());
            declaration.setOwnerScc(subject.getSocialNo());
            declaration.setOwnerCode(subject.getCustomsCode());
        }else{
            //双抬头 消费使用单位 == 客户
            Customer customer = customerService.getById(order.getCustomerId());
            declaration.setOwnerName(customer.getName());
            declaration.setOwnerScc(customer.getSocialNo());
            TsfPreconditions.checkArgument((StringUtils.isNotEmpty(declaration.getOwnerScc()) && declaration.getOwnerScc().length() == 18),new ServiceException(String.format("请先维护【%s】18位社会信用代码",declaration.getOwnerName())));
            declaration.setOwnerCode(customer.getCustomsCode());
            if(StringUtils.isEmpty(declaration.getOwnerCode())){
                //没有海关编码使用组织机构代码
                declaration.setOwnerCode(declaration.getOwnerScc().substring(8,17));
            }
        }

        declaration.setOrderId(order.getId());//订单ID
        declaration.setOrderDocNo(order.getDocNo());//订单编号
        declaration.setContrNo(TypeUtils.castToString(order.getClientNo(),order.getDocNo()));//合同号
        if(order.getDocNo().length()>4){
            declaration.setBillNo(order.getDocNo().substring(order.getDocNo().length()-4));//提运单号
        }else{
            declaration.setBillNo(order.getDocNo());//提运单号
        }
        declaration.setCustomerId(order.getCustomerId());//客户
        declaration.setDecDate(order.getOrderDate());//申报日期
        declaration.setImportDate(order.getOrderDate());//进口日期
        DeclarationStatusEnum statusEnum = DeclarationStatusEnum.WAIT_CONFIRM;//状态-待确认
        declaration.setStatusId(statusEnum.getCode());

        //订单成交方式
        TransactionModeEnum transactionMode = TransactionModeEnum.of(order.getTransactionMode());
        declaration.setTransactionMode(transactionMode.getCode());//成交方式

        declaration.setTransCosts(StringUtils.removeSpecialSymbol(order.getTransCosts()));//运费
        declaration.setInsuranceCosts(StringUtils.removeSpecialSymbol(order.getInsuranceCosts()));//保费
        declaration.setMiscCosts(StringUtils.removeSpecialSymbol(order.getMiscCosts()));//杂费

        declaration.setPackNo(order.getTotalCartonNum());//总件数
        declaration.setNetWt(order.getTotalNetWeight());//总净重
        declaration.setGrossWt(order.getTotalCrossWeight());//总毛重
        declaration.setCurrencyId(order.getCurrencyId());//币制
        declaration.setCurrencyName(order.getCurrencyName());//币制

        //系统报关单编号(用订单号)
        //String docNo = serialNumberService.generateDocNo(SerialNumberTypeEnum.DECLARATION);
        declaration.setDocNo(order.getDocNo());
        //保存报关单
        super.saveNonNull(declaration);

        List<DeclarationMember> declarationMembers = Lists.newArrayList();
        //明细数据写入
        List<ImpOrderMemberVO> memberVOS = impOrderMemberService.findOrderMaterielMember(order.getId());
        //存储海关编码
        Map<String,CustomsCodeVO> hsCodeMap = Maps.newHashMap();
        //存储申报要素
        Map<String,List<CustomsElementsVO>> elementsMap = Maps.newHashMap();
        DeclarationTemp finalTemp = temp;
        memberVOS.stream().forEach(member ->{
            TsfPreconditions.checkArgument(MaterielStatusEnum.CLASSED.getCode().equals(member.getMaterielStatusId()),new ServiceException(String.format("第【%d】行【%s】未完成归类,请联系关务归类",member.getRowNo(),member.getModel())));
            //获取海关编码详情
            CustomsCodeVO customsCodeVO = hsCodeMap.get(member.getHsCode());
            if(Objects.isNull(customsCodeVO)){
                customsCodeVO = DataCalling.getInstance().obtainHsCodeDetail(member.getHsCode());
                hsCodeMap.put(member.getHsCode(),customsCodeVO);
                List<CustomsElementsVO> elements = DataCalling.getInstance().obtainCustomsElements(member.getHsCode());
                TsfPreconditions.checkArgument(CollUtil.isNotEmpty(elements),new ServiceException(String.format("第【%d】行【%s】海关编码【%s】未找到有效申报要素",member.getRowNo(),member.getModel(),member.getHsCode())));
                elementsMap.put(member.getHsCode(),elements);
            }
            TsfPreconditions.checkArgument(Objects.nonNull(customsCodeVO),new ServiceException(String.format("第【%d】行【%s】未找到有效海关编码，请联系关务人员",member.getRowNo(),member.getModel())));

            DeclarationMember dm = new DeclarationMember();
            dm.setId(snowflake.nextId());
            dm.setDeclarationId(declaration.getId());
            dm.setModel(member.getModel());
            dm.setBrand(member.getBrand());
            dm.setName(member.getName());
            dm.setCountry(member.getCountry());
            dm.setCountryName(member.getCountryName());
            dm.setUnitCode(member.getUnitCode());
            dm.setUnitName(member.getUnitName());
            dm.setQuantity(member.getQuantity());
            dm.setUnitPrice(member.getDecUnitPrice());
            dm.setTotalPrice(member.getDecTotalPrice());
            dm.setCurrencyId(order.getCurrencyId());
            dm.setCurrencyName(order.getCurrencyName());
            dm.setNetWeight(member.getNetWeight());
            dm.setGrossWeight(member.getGrossWeight());
            dm.setCartonNum(member.getCartonNum());
            dm.setRowNo(member.getRowNo());
            dm.setHsCode(member.getHsCode());
            dm.setCiqNo(member.getCiqNo());
            dm.setDeclareSpec(member.getDeclareSpec());
            //模板明细默认数据
            dm.setDestinationCountry(finalTemp.getDestinationCountry());
            dm.setDestinationCountryName(finalTemp.getDestinationCountryName());
            dm.setDutyMode(finalTemp.getDutyMode());
            dm.setDutyModeName(finalTemp.getDutyModeName());
            dm.setDistrictCode(finalTemp.getDistrictCode());
            dm.setDistrictName(finalTemp.getDistrictName());
            dm.setCiqDestCode(finalTemp.getCiqDestCode());
            dm.setCiqDestName(finalTemp.getCiqDestName());

            //分析根据海关编码分析数据
            analysisMember(dm,customsCodeVO,elementsMap.get(dm.getHsCode()));
            declarationMembers.add(dm);
        });
        //保存明细
        declarationMemberService.savaBatch(declarationMembers);

        //写入状态历史
        statusHistoryService.saveHistory(DomainOprationEnum.DECLARATION_ADD,
                declaration.getId().toString(),Declaration.class.getName(),
                statusEnum.getCode(),statusEnum.getName(),
                "根据进口订单自动生成报关单"
        );

        //发送任务通知
        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
        taskNoticeContentDTO.setDocumentType(DomainTypeEnum.DECLARATION.getCode());
        taskNoticeContentDTO.setDocumentId(declaration.getId().toString());
        taskNoticeContentDTO.setCustomerId(declaration.getCustomerId());
        taskNoticeContentDTO.setOperationCode(DomainOprationEnum.DECLARATION_CONFIRM.getCode());
        taskNoticeContentDTO.setContent(String.format("进口订单【%s】已确定进口，消费使用单位【%s】，请尽快确定报关单", order.getDocNo(),declaration.getOwnerName()));
        taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",declaration.getDocNo()));
        taskNoticeContentService.add(taskNoticeContentDTO);
        return declaration;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Declaration createByExpOrder(ExpOrder order) {
        //获取报关单默认模板
        DeclarationTemp temp = null;
        if(Objects.nonNull(order.getDeclarationTempId())){
            temp = declarationTempService.getById(order.getDeclarationTempId());
        }
        if(Objects.isNull(temp)){
            temp = declarationTempService.obtanDefault(BillTypeEnum.EXP);
        }
        TsfPreconditions.checkArgument(Objects.nonNull(temp),new ServiceException("创建报关单失败：未找到默认出口报关模板，请联系关务维护"));
        Declaration declaration = beanMapper.map(temp,Declaration.class);
        //申报单位信息（外贸合同卖方）
        Subject subject = subjectService.getById(order.getSubjectId());
        TsfPreconditions.checkArgument(Objects.nonNull(subject),new ServiceException("申报单位信息不存在"));
        declaration.setAgentName(subject.getName());//申报单位名称
        declaration.setAgentScc(subject.getSocialNo());//统一社会信用代码
        TsfPreconditions.checkArgument((StringUtils.isNotEmpty(declaration.getAgentScc()) && declaration.getAgentScc().length() == 18),new ServiceException(String.format("请先维护【%s】18位社会信用代码",declaration.getAgentName())));
        declaration.setAgentCode(subject.getCustomsCode());//海关注册编码
        TsfPreconditions.checkArgument((StringUtils.isNotEmpty(declaration.getAgentCode()) && declaration.getAgentCode().length() == 10),new ServiceException(String.format("请先维护【%s】10位海关代码",declaration.getAgentName())));
        //境内收发货人（外贸合同卖方）
        declaration.setSubjectId(order.getSubjectId());
        //境外收发货人（外贸合同买方）
        declaration.setSubjectOverseasType(order.getSubjectOverseasType());
        declaration.setSubjectOverseasId(order.getSubjectOverseasId());
        declaration.setSubjectOverseasName(order.getSubjectOverseasName());
        //消费使用单位
        DeclareTypeEnum declareType = DeclareTypeEnum.of(order.getDeclareType());
        if(Objects.equals(DeclareTypeEnum.SINGLE,declareType)){
            //单抬头 生产销售单位 == 境内收发货人
            declaration.setOwnerName(subject.getName());
            declaration.setOwnerScc(subject.getSocialNo());
            declaration.setOwnerCode(subject.getCustomsCode());
        }else{
            //双抬头 生产销售单位 == 客户
            Customer customer = customerService.getById(order.getCustomerId());
            declaration.setOwnerName(customer.getName());
            declaration.setOwnerScc(customer.getSocialNo());
            TsfPreconditions.checkArgument((StringUtils.isNotEmpty(declaration.getOwnerScc()) && declaration.getOwnerScc().length() == 18),new ServiceException(String.format("请先维护【%s】18位社会信用代码",declaration.getOwnerName())));
            declaration.setOwnerCode(customer.getCustomsCode());
            if(StringUtils.isEmpty(declaration.getOwnerCode())){
                //没有海关编码使用组织机构代码
                declaration.setOwnerCode(declaration.getOwnerScc().substring(8,17));
            }
        }
        declaration.setOrderId(order.getId());//订单ID
        declaration.setOrderDocNo(order.getDocNo());//订单编号
        declaration.setContrNo(TypeUtils.castToString(order.getClientNo(),order.getDocNo()));//合同号
        if(order.getDocNo().length()>4){
            declaration.setBillNo(order.getDocNo().substring(order.getDocNo().length()-4));//提运单号
        }else{
            declaration.setBillNo(order.getDocNo());//提运单号
        }
        declaration.setCustomerId(order.getCustomerId());//客户
        declaration.setDecDate(order.getOrderDate());//申报日期
        declaration.setImportDate(order.getOrderDate());//进口日期
        DeclarationStatusEnum statusEnum = DeclarationStatusEnum.WAIT_CONFIRM;//状态-待确认
        declaration.setStatusId(statusEnum.getCode());
        //订单成交方式
        TransactionModeEnum transactionMode = TransactionModeEnum.of(order.getTransactionMode());
        declaration.setTransactionMode(transactionMode.getCode());//成交方式
        declaration.setTransCosts(StringUtils.removeSpecialSymbol(order.getTransCosts()));//运费
        declaration.setInsuranceCosts(StringUtils.removeSpecialSymbol(order.getInsuranceCosts()));//保费
        declaration.setMiscCosts(StringUtils.removeSpecialSymbol(order.getMiscCosts()));//杂费
        declaration.setPackNo(order.getTotalCartonNum());//总件数
        declaration.setNetWt(order.getTotalNetWeight());//总净重
        declaration.setGrossWt(order.getTotalCrossWeight());//总毛重
        declaration.setCurrencyId(order.getCurrencyId());//币制
        declaration.setCurrencyName(order.getCurrencyName());//币制
        declaration.setCusTradeCountryCode(order.getCusTradeCountry());//运抵国(地区)
        declaration.setCusTradeCountryName(order.getCusTradeCountryName());//运抵国(地区)
        declaration.setCusTradeNationCode(order.getCusTradeNationCode());//贸易国(地区)
        declaration.setCusTradeNationName(order.getCusTradeNationCodeName());//贸易国(地区)
        declaration.setDistinatePortCode(order.getDistinatePort());//指运港
        declaration.setDistinatePortName(order.getDistinatePortName());//指运港
        declaration.setCusTrafModeCode(order.getCusTrafMode());//运输方式
        declaration.setCusTrafModeName(order.getCusTrafModeName());//运输方式
        //系统报关单编号(用订单号)
        declaration.setDocNo(order.getDocNo());
        //保存报关单
        super.saveNonNull(declaration);

        List<DeclarationMember> declarationMembers = Lists.newArrayList();
        //明细数据写入
        List<ExpOrderMemberVO> memberVOS = expOrderMemberService.findOrderMaterielMember(order.getId());
        //存储海关编码
        Map<String,CustomsCodeVO> hsCodeMap = Maps.newHashMap();
        //存储申报要素
        Map<String,List<CustomsElementsVO>> elementsMap = Maps.newHashMap();
        for(ExpOrderMemberVO member : memberVOS){
            TsfPreconditions.checkArgument(MaterielStatusEnum.CLASSED.getCode().equals(member.getMaterielStatusId()),new ServiceException(String.format("第【%d】行【%s】未完成归类,请联系关务归类",member.getRowNo(),member.getModel())));
            //获取海关编码详情
            CustomsCodeVO customsCodeVO = hsCodeMap.get(member.getHsCode());
            if(Objects.isNull(customsCodeVO)){
                customsCodeVO = DataCalling.getInstance().obtainHsCodeDetail(member.getHsCode());
                hsCodeMap.put(member.getHsCode(),customsCodeVO);
                List<CustomsElementsVO> elements = DataCalling.getInstance().obtainCustomsElements(member.getHsCode());
                TsfPreconditions.checkArgument(CollUtil.isNotEmpty(elements),new ServiceException(String.format("第【%d】行【%s】海关编码【%s】未找到有效申报要素",member.getRowNo(),member.getModel(),member.getHsCode())));
                elementsMap.put(member.getHsCode(),elements);
            }
            TsfPreconditions.checkArgument(Objects.nonNull(customsCodeVO),new ServiceException(String.format("第【%d】行【%s】未找到有效海关编码，请联系关务人员",member.getRowNo(),member.getModel())));
            DeclarationMember dm = new DeclarationMember();
            dm.setId(snowflake.nextId());
            dm.setDeclarationId(declaration.getId());
            dm.setModel(member.getModel());
            dm.setBrand(member.getBrand());
            dm.setName(member.getName());
            dm.setCountry(member.getCountry());
            dm.setCountryName(member.getCountryName());
            dm.setUnitCode(member.getUnitCode());
            dm.setUnitName(member.getUnitName());
            dm.setQuantity(member.getQuantity());
            dm.setUnitPrice(member.getDecUnitPrice());
            dm.setTotalPrice(member.getDecTotalPrice());
            dm.setCurrencyId(order.getCurrencyId());
            dm.setCurrencyName(order.getCurrencyName());
            dm.setNetWeight(member.getNetWeight());
            dm.setGrossWeight(member.getGrossWeight());
            dm.setCartonNum(member.getCartonNum());
            dm.setRowNo(member.getRowNo());
            dm.setHsCode(member.getHsCode());
            dm.setCiqNo("");
            dm.setDeclareSpec(member.getDeclareSpec());
            // 最终目的国
            dm.setDestinationCountry(order.getDestinationCountry());
            dm.setDestinationCountryName(order.getDestinationCountryName());
            dm.setDutyMode(temp.getDutyMode());
            dm.setDutyModeName(temp.getDutyModeName());
            dm.setDistrictCode(order.getDistrictCode());
            dm.setDistrictName(order.getDistrictCodeName());
            dm.setCiqDestCode(temp.getCiqDestCode());
            dm.setCiqDestName(temp.getCiqDestName());
            //分析根据海关编码分析数据
            analysisMember(dm,customsCodeVO,elementsMap.get(dm.getHsCode()),BillTypeEnum.EXP);
            declarationMembers.add(dm);
        }
        //保存明细
        declarationMemberService.savaBatch(declarationMembers);
        //写入状态历史
        statusHistoryService.saveHistory(DomainOprationEnum.DECLARATION_ADD,
                declaration.getId().toString(),Declaration.class.getName(),
                statusEnum.getCode(),statusEnum.getName(),
                "根据出口订单自动生成报关单"
        );
        //发送任务通知
        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
        taskNoticeContentDTO.setDocumentType(DomainTypeEnum.DECLARATION.getCode());
        taskNoticeContentDTO.setDocumentId(declaration.getId().toString());
        taskNoticeContentDTO.setCustomerId(declaration.getCustomerId());
        taskNoticeContentDTO.setOperationCode(DomainOprationEnum.EXP_DECLARATION_CONFIRM.getCode());
        taskNoticeContentDTO.setContent(String.format("出口订单【%s】已确定出口，生产销售单位【%s】，请尽快确定报关单", order.getDocNo(),declaration.getOwnerName()));
        taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",declaration.getDocNo()));
        taskNoticeContentService.add(taskNoticeContentDTO);
        return declaration;
    }

    @Override
    public Declaration findByDocNo(String docNo) {
        Declaration query = new Declaration();
        query.setDocNo(docNo);
        return declarationMapper.selectOne(query);
    }

    @Override
    public Declaration findByOrderId(Long orderId) {
        Declaration query = new Declaration();
        query.setOrderId(orderId);
        return declarationMapper.selectOne(query);
    }

    public void analysisMember(DeclarationMember dm, CustomsCodeVO customsCodeVO, List<CustomsElementsVO> elements){
        analysisMember(dm,customsCodeVO,elements,BillTypeEnum.IMP);
    }
    public void analysisMember(DeclarationMember dm,CustomsCodeVO customsCodeVO,List<CustomsElementsVO> elements,BillTypeEnum billTypeEnum){
        //法定第一计量单位
        dm.setUnit1Code(customsCodeVO.getUnitCode());
        dm.setUnit1Name(customsCodeVO.getUnitName());
        List<String> sameUnits = Arrays.asList("台","只","张");

        if(dm.getUnitName().equals(dm.getUnit1Name()) || ("个".equals(dm.getUnitName()) && sameUnits.contains(dm.getUnit1Name()))){
            dm.setQuantity1(dm.getQuantity());
        }else if(Objects.equals("十"+dm.getUnitName(),dm.getUnit1Name())){
            dm.setQuantity1(dm.getQuantity().divide(BigDecimal.valueOf(10),4,BigDecimal.ROUND_HALF_UP));
        }else if(Objects.equals("百"+dm.getUnitName(),dm.getUnit1Name())){
            dm.setQuantity1(dm.getQuantity().divide(BigDecimal.valueOf(100),4,BigDecimal.ROUND_HALF_UP));
        }else if(Objects.equals("千"+dm.getUnitName(),dm.getUnit1Name())){
            dm.setQuantity1(dm.getQuantity().divide(BigDecimal.valueOf(1000),4,BigDecimal.ROUND_HALF_UP));
        }else if(Objects.equals("十"+dm.getUnit1Name(),dm.getUnitName())){
            dm.setQuantity1(dm.getQuantity().multiply(BigDecimal.valueOf(10)).setScale(4,BigDecimal.ROUND_HALF_UP));
        }else if(Objects.equals("百"+dm.getUnit1Name(),dm.getUnitName())){
            dm.setQuantity1(dm.getQuantity().multiply(BigDecimal.valueOf(100)).setScale(4,BigDecimal.ROUND_HALF_UP));
        }else if(Objects.equals("千"+dm.getUnit1Name(),dm.getUnitName())){
            dm.setQuantity1(dm.getQuantity().multiply(BigDecimal.valueOf(1000)).setScale(4,BigDecimal.ROUND_HALF_UP));
        }else if(Objects.equals("万"+dm.getUnit1Name(),dm.getUnitName())){
            dm.setQuantity1(dm.getQuantity().multiply(BigDecimal.valueOf(10000)).setScale(4,BigDecimal.ROUND_HALF_UP));
        }else if("千克".equals(dm.getUnit1Name())){
            //净重
            dm.setQuantity1(dm.getNetWeight());
        }else{
            throw new ServiceException(String.format("第【%d】行【%s】成交单位【%s】转法一单位【%s】失败",dm.getRowNo(),dm.getModel(),dm.getUnitName(),dm.getUnit1Name()));
        }

        //法定第二计量单位
        dm.setUnit2Code(customsCodeVO.getSunitCode());
        dm.setUnit2Name(customsCodeVO.getSunitName());
        if(StringUtils.isNotEmpty(dm.getUnit2Code())){
            if(dm.getUnitName().equals(dm.getUnit2Name()) || ("个".equals(dm.getUnitName()) && sameUnits.contains(dm.getUnit2Name()))){
                dm.setQuantity2(dm.getQuantity());
            }else if(Objects.equals("十"+dm.getUnitName(),dm.getUnit2Name())){
                dm.setQuantity2(dm.getQuantity().divide(BigDecimal.valueOf(10),4,BigDecimal.ROUND_HALF_UP));
            }else if(Objects.equals("百"+dm.getUnitName(),dm.getUnit2Name())){
                dm.setQuantity2(dm.getQuantity().divide(BigDecimal.valueOf(100),4,BigDecimal.ROUND_HALF_UP));
            }else if(Objects.equals("千"+dm.getUnitName(),dm.getUnit2Name())){
                dm.setQuantity2(dm.getQuantity().divide(BigDecimal.valueOf(1000),4,BigDecimal.ROUND_HALF_UP));
            }else if(Objects.equals("十"+dm.getUnit2Name(),dm.getUnitName())){
                dm.setQuantity2(dm.getQuantity().multiply(BigDecimal.valueOf(10)).setScale(4,BigDecimal.ROUND_HALF_UP));
            }else if(Objects.equals("百"+dm.getUnit2Name(),dm.getUnitName())){
                dm.setQuantity2(dm.getQuantity().multiply(BigDecimal.valueOf(100)).setScale(4,BigDecimal.ROUND_HALF_UP));
            }else if(Objects.equals("千"+dm.getUnit2Name(),dm.getUnitName())){
                dm.setQuantity2(dm.getQuantity().multiply(BigDecimal.valueOf(1000)).setScale(4,BigDecimal.ROUND_HALF_UP));
            }else if(Objects.equals("万"+dm.getUnit2Name(),dm.getUnitName())){
                dm.setQuantity2(dm.getQuantity().multiply(BigDecimal.valueOf(10000)).setScale(4,BigDecimal.ROUND_HALF_UP));
            }else if("千克".equals(dm.getUnit2Name())){
                //净重
                dm.setQuantity2(dm.getNetWeight());
            }else{
                throw new ServiceException(String.format("第【%d】行【%s】成交单位【%s】转法二单位【%s】失败",dm.getRowNo(),dm.getModel(),dm.getUnitName(),dm.getUnit2Name()));
            }
        }

        TsfPreconditions.checkArgument(StringUtils.isNotEmpty(dm.getDeclareSpec()),new ServiceException(String.format("第【%d】行【%s】物料未找到有效申报要素，请联系关务归类",dm.getRowNo(),dm.getModel())));
        //分析申报要素 加空额防止最后一位是|
        String declareSpec = StringUtils.removeSpecialSymbol(dm.getDeclareSpec())+" ";
        List<String> customsSpecs = Arrays.asList(declareSpec.split("\\|"));
        if(elements.size()!=customsSpecs.size()){
            TsfPreconditions.checkArgument(StringUtils.isNotEmpty(dm.getDeclareSpec()),new ServiceException(String.format("第【%d】行【%s】物料归类申报要素错误，请联系关务重新归类",dm.getRowNo(),dm.getModel())));
        }
        List<String> declareSpecs = Lists.newArrayList();
        for(int i=0;i<elements.size();i++){
            CustomsElementsVO ce = elements.get(i);
            String evalue = StringUtils.removeSpecialSymbol(customsSpecs.get(i));
            if(MaterielUtil.ELEMENT_BRAND_MARK.equals(ce.getName())){
                if(Objects.equals(billTypeEnum,BillTypeEnum.IMP)){  // 进口
                    // 品牌
                    String brandZh = materielBrandService.findZhByNameEn(dm.getBrand());
                    if(StringUtils.isEmpty(brandZh)){
                        throw new ServiceException(String.format("外文品牌：%s 未找到中文名称",dm.getBrand()));
                    }
                    evalue = MaterielUtil.formatGoodsBrand(dm.getBrand(),brandZh);
                }else if(Objects.equals(billTypeEnum,BillTypeEnum.EXP)){  // 出口
                    evalue = MaterielUtil.formatGoodsBrand(dm.getBrand());
                }
            }else if(MaterielUtil.ELEMENT_MODEL_MARK.equals(ce.getName())){
                // 型号
                evalue = MaterielUtil.formatGoodsModel(dm.getModel());
            }
            declareSpecs.add(evalue);
        }
        dm.setDeclareSpec(String.join("|",declareSpecs));
    }

    @Override
    public PageInfo<DeclarationListVO> pageList(DeclarationQTO qto) {
        //如果传了动态日期字段，检查是否包含该字段以防数据库报错
        if(StringUtils.isNotEmpty(qto.getQueryDate())) {
            TsfPreconditions.checkArgument(ClassUtil.containsField(Declaration.class,qto.getQueryDate()),new ServiceException("日期查询类型参数错误"));
        }
        PageHelper.startPage(qto.getPage(),qto.getLimit());
        Map<String,Object> params = beanMapper.map(qto, Map.class);
        List<DeclarationListVO> list = declarationMapper.list(params);
        return new PageInfo(list);
    }

    @Override
    public List<DeclarationListVO> waitSendList(List<String> ids) {
        if(CollUtil.isEmpty(ids)){return Lists.newArrayList();}
        Map<String,Object> params = Maps.newLinkedHashMap();
        params.put("ids",ids);
        return declarationMapper.list(params);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void confirm(ConfirmDeclarationDTO dto) {
        String lockKey = DistributedLockEnum.DECLARATION_CONFIRM.getCode() + ":" + dto.getId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if(!isLock) {
                log.error(String.format("未获取到锁，报关单id：【%s】",dto.getId()));
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            //单据类型
            BillTypeEnum billTypeEnum = BillTypeEnum.of(dto.getBillType());
            TaskDTO taskDTO = new TaskDTO();
            taskDTO.setDocumentId(dto.getId());
            DomainTypeEnum domainTypeEnum = DomainTypeEnum.DECLARATION;
            taskDTO.setDocumentClass(domainTypeEnum.getCode());
            taskDTO.setNewStatusCode(DeclarationStatusEnum.WAIT_DECLARE.getCode());
            DomainOprationEnum oprationEnum = (Objects.equals(billTypeEnum,BillTypeEnum.IMP))?DomainOprationEnum.DECLARATION_CONFIRM:DomainOprationEnum.EXP_DECLARATION_CONFIRM;
            taskDTO.setOperation(oprationEnum.getCode());
            taskDTO.setMemo("报关单确定");
            Declaration declaration = commonService.changeDocumentStatus(taskDTO);
            //验证报关单明细数据
            checkCustomsElement(declaration);
            //保存报关单数据
            declaration.setId(declaration.getId());
            declaration.setAgentName(dto.getAgentName());
            declaration.setAgentScc(dto.getAgentScc());
            declaration.setAgentCode(dto.getAgentCode());
            declaration.setCustomMasterCode(dto.getCustomMasterCode());
            declaration.setCustomMasterName(dto.getCustomMasterName());
            declaration.setIePortCode(dto.getIePortCode());
            declaration.setIePortName(dto.getIePortName());
            declaration.setCiqEntyPortCode(dto.getCiqEntyPortCode());
            declaration.setCiqEntyPortName(dto.getCiqEntyPortName());
            declaration.setCusTradeCountryCode(dto.getCusTradeCountryCode());
            declaration.setCusTradeCountryName(dto.getCusTradeCountryName());
            declaration.setCusTradeNationCode(dto.getCusTradeNationCode());
            declaration.setCusTradeNationName(dto.getCusTradeNationName());
            declaration.setDestPortCode(dto.getDestPortCode());
            declaration.setDestPortName(dto.getDestPortName());
            declaration.setDistinatePortCode(dto.getDistinatePortCode());
            declaration.setDistinatePortName(dto.getDistinatePortName());
            declaration.setCusTrafModeCode(dto.getCusTrafModeCode());
            declaration.setCusTrafModeName(dto.getCusTrafModeName());
            declaration.setSupvModeCdde(dto.getSupvModeCdde());
            declaration.setSupvModeCddeName(dto.getSupvModeCddeName());
            declaration.setCutModeCode(dto.getCutModeCode());
            declaration.setCutModeName(dto.getCutModeName());
            declaration.setWrapTypeCode(dto.getWrapTypeCode());
            declaration.setWrapTypeName(dto.getWrapTypeName());
            declaration.setRecordNo(dto.getRecordNo());
            declaration.setLicenseNo(dto.getLicenseNo());
            declaration.setCusVoyageNo(dto.getCusVoyageNo());
            declaration.setContainerNo(dto.getContainerNo());
            declaration.setBillNo(dto.getBillNo());
            declaration.setGoodsPlace(dto.getGoodsPlace());
            declaration.setMarkNo(dto.getMarkNo());
            declaration.setMemo(dto.getMemo());
            super.updateById(declaration);

            //任务通知
            TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
            taskNoticeContentDTO.setDocumentType(domainTypeEnum.getCode());
            taskNoticeContentDTO.setDocumentId(declaration.getId().toString());
            taskNoticeContentDTO.setCustomerId(declaration.getCustomerId());
            DomainOprationEnum domainOprationEnum = (Objects.equals(billTypeEnum,BillTypeEnum.IMP))?DomainOprationEnum.DECLARATION_DECLARE:DomainOprationEnum.EXP_DECLARATION_DECLARE;
            taskNoticeContentDTO.setOperationCode(domainOprationEnum.getCode());
            String str = (Objects.equals(billTypeEnum,BillTypeEnum.IMP))?"进口订单【%s】消费使用单位【%s】报关单已确认，请尽快确认申报":"出口订单【%s】生产销售单位【%s】报关单已确认，请尽快确认申报";
            taskNoticeContentDTO.setContent(String.format(str, declaration.getOrderDocNo(),declaration.getOwnerName()));
            taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",declaration.getDocNo()));
            taskNoticeContentService.add(taskNoticeContentDTO);
        } catch (InterruptedException e) {
            log.error("确认报关单获取锁异常",e);
            throw new ServiceException("服务拥挤，请稍后再试");
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void backInspection(Long id, String info) {
        Declaration declaration = super.getById(id);
        if(Objects.isNull(declaration) || !Objects.equals(DeclarationStatusEnum.WAIT_CONFIRM,DeclarationStatusEnum.of(declaration.getStatusId()))){
            throw new ServiceException("报关单不存在或不处于待确认状态不允许退单");
        }
        // 删除报关单所有信息
        removeAllByDeclaration(declaration);
        switch (BillTypeEnum.of(declaration.getBillType())){
            case IMP:
                impOrderService.backInspection(declaration.getOrderId(),info);
                break;
            case EXP:
                expOrderService.backInspection(declaration.getOrderId(),info);
                break;
            default:
                throw new ServiceException("单据类型错误");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void removeAllByDeclaration(Declaration declaration){
        // 删除报关单明细数据
        declarationMemberService.removeByDeclarationId(declaration.getId());
        // 删除历史状态
        statusHistoryService.removeHistory(declaration.getId().toString(), Declaration.class.getName());
        // 删除文件信息
        uploadFileService.deleteByDocId(declaration.getId().toString());
        //删除主单
        super.removeById(declaration.getId());
        //清空任务
        clearTaskNotice(declaration.getId());
    }

    //清空任务通知
    public void clearTaskNotice(Long id){
        taskNoticeContentService.deleteTaskNotice(DomainTypeEnum.DECLARATION.getCode(), id, "");
    }

    @Override
    public DeclarationPlusDetailVO detail(Long id,String operation) {
        DeclarationVO declaration = mainDetail(id,operation);
        DeclarationVO declarationVO = beanMapper.map(declaration,DeclarationVO.class);
        if(StringUtils.isEmpty(declarationVO.getConsignorName())) {
            BillTypeEnum billTypeEnum = BillTypeEnum.of(declaration.getBillType());
            BuyerSellerInfo buyerSellerInfo = subjectBiz.getBySubjectOverseasType(billTypeEnum,declarationVO.getSubjectOverseasId(),declarationVO.getSubjectOverseasType());
            declarationVO.setConsignorName(buyerSellerInfo.getName());
        }
        List<DeclarationMemberVO> members = memberDetail(id);
        return new DeclarationPlusDetailVO(declarationVO,members);
    }

    @Override
    public DeclarationVO mainDetail(Long id, String operation) {
        DeclarationVO declaration = declarationMapper.detail(id);
        TsfPreconditions.checkArgument(Objects.nonNull(declaration),new ServiceException("报关单数据不存在"));
        if(StringUtils.isNotEmpty(operation)){
            DomainStatus.getInstance().check(DomainOprationEnum.of(operation), declaration.getStatusId());
        }
        return declaration;
    }

    @Override
    public List<DeclarationMemberVO> memberDetail(Long id) {
        return declarationMemberService.findByDeclarationId(id);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void confirmDeclare(TaskDTO dto) {
        String lockKey = DistributedLockEnum.DECLARATION_REVIEW.getCode()  + ":" + dto.getDocumentId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("确认申报报关单未获取到锁，报关单id：【%s】", dto.getDocumentId()));
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            Declaration declaration = commonService.changeDocumentStatus(dto);
            DeclarationStatusEnum statusEnum = DeclarationStatusEnum.of(declaration.getStatusId());
            BillTypeEnum billTypeEnum = BillTypeEnum.of(declaration.getBillType());
            if(Objects.equals(statusEnum,DeclarationStatusEnum.WAIT_CONFIRM)){
                // 发送通知退回
                TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
                taskNoticeContentDTO.setDocumentType(DomainTypeEnum.DECLARATION.getCode());
                taskNoticeContentDTO.setDocumentId(declaration.getId().toString());
                taskNoticeContentDTO.setCustomerId(declaration.getCustomerId());
                DomainOprationEnum domainOprationEnum = (Objects.equals(billTypeEnum,BillTypeEnum.IMP))?DomainOprationEnum.DECLARATION_CONFIRM:DomainOprationEnum.EXP_DECLARATION_CONFIRM;
                taskNoticeContentDTO.setOperationCode(domainOprationEnum.getCode());
                String str = (Objects.equals(billTypeEnum,BillTypeEnum.IMP))?"进口报关单【%s】已被退回请重新确认：%s":"出口报关单【%s】已被退回请重新确认：%s";
                taskNoticeContentDTO.setContent(String.format(str, declaration.getDocNo(),StringUtils.removeSpecialSymbol(dto.getMemo())));
                taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",declaration.getDocNo()));
                taskNoticeContentService.add(taskNoticeContentDTO);
            }else if(Objects.equals(statusEnum,DeclarationStatusEnum.IN_DECLARE)){
                // 检查报关单是否公路运输
                if("4".equals(declaration.getCusTrafModeCode())){
                    // 检查跨境运输 核对进出境关别，六联单号，集装箱号
                    CrossBorderWaybill crossBorderWaybill = null;
                    switch (billTypeEnum){
                        case IMP:
                            crossBorderWaybill = crossBorderWaybillService.findByImpOrderId(declaration.getOrderId());
                            break;
                        case EXP:
                            crossBorderWaybill = crossBorderWaybillService.findByExpOrderId(declaration.getOrderId());
                            break;
                        default:
                            throw new ServiceException("单据类型错误");
                    }
                    TsfPreconditions.checkArgument(Objects.nonNull(crossBorderWaybill),new ServiceException("公路运输请先绑定跨境运输单"));
                    if(!StringUtils.removeSpecialSymbol(declaration.getIePortCode()).equals(StringUtils.removeSpecialSymbol(crossBorderWaybill.getCustomsCode()))){
                        throw new ServiceException(String.format("报关单进出境关别与跨境运单【%s】通关口岸不一致",crossBorderWaybill.getDocNo()));
                    }
                    if(!StringUtils.removeSpecialSymbol(declaration.getCusVoyageNo()).equals(StringUtils.removeSpecialSymbol(crossBorderWaybill.getWaybillNo()))){
                        throw new ServiceException(String.format("报关单航次号(六联单号)与跨境运单【%s】运输单号(六联单号)不一致",crossBorderWaybill.getDocNo()));
                    }
                    if(!StringUtils.removeSpecialSymbol(declaration.getContainerNo()).equals(StringUtils.removeSpecialSymbol(crossBorderWaybill.getContainerNo()))){
                        throw new ServiceException(String.format("报关单集装箱号与跨境运单【%s】集装箱号不一致",crossBorderWaybill.getDocNo()));
                    }
                }
            }else{
                throw new ServiceException("当前状态不支持此操作");
            }
            // 清空确认申报任务
            DomainOprationEnum domainOprationEnum = (Objects.equals(billTypeEnum,BillTypeEnum.IMP))?DomainOprationEnum.DECLARATION_DECLARE:DomainOprationEnum.EXP_DECLARATION_DECLARE;
            taskNoticeContentService.deleteTaskNotice(DomainTypeEnum.DECLARATION.getCode(), declaration.getId(), domainOprationEnum.getCode());
        } catch (InterruptedException e) {
            log.error(String.format("确认申报报关单获取锁异，报关单id：【%s】", dto.getDocumentId()),e);
            throw new ServiceException("服务拥挤，请稍后再试");
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void declarationCompleted(TaskDTO dto){
        String lockKey = DistributedLockEnum.DECLARATION_COMPLETED.getCode() + ":" + dto.getDocumentId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("报关单申报完成未获取到锁，报关单id：【%s】", dto.getDocumentId()));
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            Declaration declaration = commonService.changeDocumentStatus(dto);
            DeclarationStatusEnum statusEnum = DeclarationStatusEnum.of(declaration.getStatusId());
            BillTypeEnum billTypeEnum = BillTypeEnum.of(declaration.getBillType());
            switch (statusEnum){
                case WAIT_CONFIRM://待确认
                    //任务通知
                    TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
                    taskNoticeContentDTO.setDocumentType(DomainTypeEnum.DECLARATION.getCode());
                    taskNoticeContentDTO.setDocumentId(declaration.getId().toString());
                    taskNoticeContentDTO.setCustomerId(declaration.getCustomerId());
                    DomainOprationEnum oprationEnum = Objects.equals(billTypeEnum,BillTypeEnum.IMP)?DomainOprationEnum.DECLARATION_CONFIRM:DomainOprationEnum.EXP_DECLARATION_CONFIRM;
                    taskNoticeContentDTO.setOperationCode(oprationEnum.getCode());
                    String str = (Objects.equals(billTypeEnum,BillTypeEnum.IMP))?"进口报关单【%s】已被退回请重新确认：%s":"出口报关单【%s】已被退回请重新确认：%s";
                    taskNoticeContentDTO.setContent(String.format(str, declaration.getDocNo(),StringUtils.removeSpecialSymbol(dto.getMemo())));
                    taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",declaration.getDocNo()));
                    taskNoticeContentService.add(taskNoticeContentDTO);
                    break;
                case COMPLETE://申报完成
                    switch (billTypeEnum){
                        case IMP:
                            //订单状态（待通关）
                            impOrderService.declarationCompleted(declaration.getOrderId());
                            //驱动国内送货单
                            deliveryWaybillService.confirmByOrder(declaration.getOrderId());
                            break;
                        case EXP:
                            //订单状态（待通关）
                            expOrderService.declarationCompleted(declaration.getOrderId());
                            break;
                        default:
                            throw new ServiceException("单据类型错误");
                    }
                    break;
                default:
                    throw new ServiceException("状态错误");
            }

        } catch (InterruptedException e) {
            log.error(String.format("报关单申报完成获取锁异，报关单id：【%s】", dto.getDocumentId()),e);
            throw new ServiceException("服务拥挤，请稍后再试");
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void importSinglewindow(Long id) {
        importSinglewindow(super.getById(id));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void importSinglewindow(Declaration declaration) {
        //验证报关单明细数据
        List<DeclarationMemberVO> members = checkCustomsElement(declaration);
        //生成报关单报文
        DeclareBizSendDTO sendDTO = declareBiz.declareTempSave("scm",declaration,members);

        //记录单一窗口日志
        LogSingleWindow logSingleWindow = new LogSingleWindow();
        logSingleWindow.setDomainId(declaration.getDocNo());
        logSingleWindow.setDomainType(SingleWindowLogEnum.DECLARE.getCode());
        logSingleWindow.setNode("确定发送");
        logSingleWindow.setCreateBy(SecurityUtil.getCurrentPersonName());
        logSingleWindow.setDateCreated(LocalDateTime.now());
        logSingleWindow.setMemo("创建报关单报文信息，发送至单一窗口");
        logSingleWindowService.saveNonNull(logSingleWindow);
        //修改报关单单一窗口导入状态为导入中
        Declaration update = new Declaration();
        update.setDeclarationStatus(SingleWindowDeclarationStatusEnum.IMPORTING.getCode());
        declarationMapper.updateByExampleSelective(update, Example.builder(Declaration.class).where(TsfWeekendSqls.<Declaration>custom().andEqualTo(false,Declaration::getId,declaration.getId())).build());
        //发送消息通知客户端下载报文
        declareNoticeSender.send(ProducerRabbitConfig.NOTICE_EXCHANGE_NAME,ProducerRabbitConfig.DECLARE_SEND, JSONObject.toJSONString(sendDTO));
    }

    /**
     * 验证明细申报要素
     */
    public List<DeclarationMemberVO> checkCustomsElement(Declaration declaration) {
        //获取报关单明细
        List<DeclarationMemberVO> members = declarationMemberService.findByDeclarationId(declaration.getId());
        TsfPreconditions.checkArgument(CollUtil.isNotEmpty(members),new ServiceException("该报关单异常，没有明细数据"));
        Map<String, CustomsCodeVO> customsCodeVOMap = Maps.newHashMapWithExpectedSize(members.size());
        for(int i=0,size = members.size(); i < size;i++) {
            DeclarationMemberVO declarationMemberVO = members.get(i);
            //验证报关规格
            TsfPreconditions.checkArgument(declarationMemberVO.getDeclareSpec().length() <= 255,new ServiceException("报关规格过长,不能大于255"));
            //验证海关编码
            TsfPreconditions.checkArgument(StringUtils.isNotEmpty(declarationMemberVO.getHsCode()),new ServiceException(String.format("报关单明细第%d行海关编码不能为空",declarationMemberVO.getRowNo())));
            CustomsCodeVO customsCodeVO;
            if(customsCodeVOMap.containsKey(declarationMemberVO.getHsCode())) {
                customsCodeVO = customsCodeVOMap.get(declarationMemberVO.getHsCode());
            } else {
                customsCodeVO = systemCacheService.getCustomsCodeInfo(declarationMemberVO.getHsCode());
                Optional.ofNullable(customsCodeVO).orElseThrow(()->new ServiceException(String.format("报关单明细第%d行海关编码【%s】错误",declarationMemberVO.getRowNo(),declarationMemberVO.getHsCode())));
                customsCodeVOMap.put(declarationMemberVO.getHsCode(),customsCodeVO);
            }
            customsCodeVOMap.put(declarationMemberVO.getHsCode(),customsCodeVO);
            //申报要素最后一位补空格
            String declareSpec = StringUtils.removeSpecialSymbol(declarationMemberVO.getDeclareSpec())+" ";
            String[] customsSpecs = declareSpec.split("\\|");
            //根据海关编码获取申报要素
            List<CustomsElementsVO> elements = systemCacheService.getCustomsElementsByHsCode(declarationMemberVO.getHsCode());
            log.debug("海关编码【{}】基础数据包含【{}】条要素信息",declarationMemberVO.getHsCode(),elements.size());
            if(ArrayUtils.isEmpty(customsSpecs)) {
                log.error(String.format("第【%d】条报关单明细，型号【%s】，品名【%s】，海关编码【%s】规格参数为空",declarationMemberVO.getRowNo(),declarationMemberVO.getModel(),declarationMemberVO.getName(),declarationMemberVO.getHsCode()));
                throw new ServiceException(String.format("第【%d】条报关单明细，请先完善报关规格",declarationMemberVO.getRowNo()));
            }
            if(customsSpecs.length != elements.size()) {
                log.error(String.format("第【%d】条报关单明细，型号【%s】，品名【%s】，海关编码【%s】规格参数【%s】,与基础数据要素个数不匹配",declarationMemberVO.getRowNo(),declarationMemberVO.getModel(),declarationMemberVO.getName(),declarationMemberVO.getHsCode(),declarationMemberVO.getDeclareSpec()));
                throw new ServiceException(String.format("第【%d】条报关单明细，请先完善报关规格",declarationMemberVO.getRowNo()));
            }
            //检查申报要素必填项是否填写
            IntStream.range(0,elements.size()).forEach(idx->{
                if(Objects.equals(elements.get(idx).getIsNeed(),Boolean.TRUE) && StringUtils.isEmpty(customsSpecs[idx])) {
                    throw new ServiceException(String.format("第【%d】条报关单明细，要素项【%s】必须填写",declarationMemberVO.getRowNo(),elements.get(idx).getName()));
                }
            });
            //检查法定单位、法定数量
            if(StringUtils.isNotEmpty(declarationMemberVO.getUnit1Code()) && (Objects.isNull(declarationMemberVO.getQuantity1()) || declarationMemberVO.getQuantity1().compareTo(BigDecimal.ZERO) != 1)) {
                throw new ServiceException(String.format("第【%d】条报关单明细，请填写法一数量",declarationMemberVO.getRowNo()));
            }
            if(StringUtils.isNotEmpty(declarationMemberVO.getUnit2Code()) && (Objects.isNull(declarationMemberVO.getQuantity2()) || declarationMemberVO.getQuantity2().compareTo(BigDecimal.ZERO) != 1)) {
                throw new ServiceException(String.format("第【%d】条报关单明细，请填写法二数量",declarationMemberVO.getRowNo()));
            }
        }
        return members;
    }

    @Override
    public ContractPlusVO contractData(Long id) {
        ContractPlusVO contractPlusVO = new ContractPlusVO();
        ContractVO contractVO = declarationMapper.contractData(id);
        BillTypeEnum billTypeEnum = BillTypeEnum.of(contractVO.getBillType());
        //设置卖方信息
        contractVO.setSellerNameEn(contractVO.getSubjectOverseasName());
        BuyerSellerInfo buyerSellerInfo = subjectBiz.getBySubjectOverseasType(billTypeEnum,contractVO.getSubjectOverseasId(),contractVO.getSubjectOverseasType());
        contractVO.setSellerName(buyerSellerInfo.getName());
        contractVO.setSellerAddress(buyerSellerInfo.getAddress());
        contractVO.setSellerAddressEn(buyerSellerInfo.getAddressEn());
        contractVO.setSellerTel(buyerSellerInfo.getTel());
        contractVO.setSellerFax(buyerSellerInfo.getFax());
        contractVO.setSellerChapter(buyerSellerInfo.getChapter());
        if(StringUtils.isEmpty(contractVO.getSellerNameEn())) {
            contractVO.setSellerNameEn(buyerSellerInfo.getNameEn());
        }

        TsfPreconditions.checkArgument(Objects.nonNull(contractVO),new ServiceException("合同数据不存在"));
        if(Objects.equals(BillTypeEnum.of(contractVO.getBillType()),BillTypeEnum.EXP)){
            // 出口买卖双方反过来
            ContractVO contract = beanMapper.map(contractVO,ContractVO.class);
            contractVO.setBuyerName(contract.getSellerName());
            contractVO.setBuyerNameEn(contract.getSellerNameEn());
            contractVO.setBuyerAddress(contract.getSellerAddress());
            contractVO.setBuyerAddressEn(contract.getSellerAddressEn());
            contractVO.setBuyerTel(contract.getSellerTel());
            contractVO.setBuyerFax(contract.getSellerFax());
            contractVO.setBuyerChapter(contract.getSellerChapter());

            contractVO.setSellerName(contract.getBuyerName());
            contractVO.setSellerNameEn(contract.getBuyerNameEn());
            contractVO.setSellerAddress(contract.getBuyerAddress());
            contractVO.setSellerAddressEn(contract.getBuyerAddressEn());
            contractVO.setSellerTel(contract.getBuyerTel());
            contractVO.setSellerFax(contract.getBuyerFax());
            contractVO.setSellerChapter(contract.getBuyerChapter());

            // 合同日期 出口日期向前推45天
            contractVO.setContractDate(contractVO.getImportDate().plusDays(-45));
            // 装运期限 出口日期向前推15天
            contractVO.setShipmentDate(contractVO.getImportDate().plusDays(-15));
            // 箱单发票日期 出口日期向前推1天
            contractVO.setInvoiceDate(contractVO.getImportDate().plusDays(-1));
        }else{
            // 合同日期 进口日期向前推5天
            contractVO.setContractDate(contractVO.getImportDate().plusDays(-5));
            // 装运期限 进口日期向后推25天
            contractVO.setShipmentDate(contractVO.getImportDate().plusDays(25));
            // 箱单发票日期 = 进口日期
            contractVO.setInvoiceDate(contractVO.getImportDate());
        }
        List<DeclarationMemberVO> memberVOS = memberDetail(id);
        contractPlusVO.setMain(contractVO);
        contractPlusVO.setMembers(beanMapper.mapAsList(memberVOS,ContractMemberVO.class));
        return contractPlusVO;
    }

    @Override
    public int updateByWaybill(WaybillBindOrderUpdateDecDTO dto) {
        return declarationMapper.updateByWaybill(dto);
    }

    @Override
    public Declaration getByOrderId(Long orderId) {
        Declaration condition = new Declaration();
        condition.setOrderId(orderId);
        return super.getOne(condition);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void resetByCrossBorderWaybill(Long id) {
        declarationMapper.resetByCrossBorderWaybill(id);
    }

    /**
     * 报关生成公路舱单-进出口通用
     * @param id
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void generateManifest(Long id) {
        Declaration declaration = super.getById(id);
        Optional.ofNullable(declaration).orElseThrow(()->new ServiceException("报关单不存在"));
        //判断是否为公路运输
        TsfPreconditions.checkArgument(Objects.equals(declaration.getCusTrafModeCode(),"4"),new ServiceException(String.format("报关单%s不是公路运输，不能生成公路舱单",declaration.getDocNo())));
        //提运单号不能为空
        TsfPreconditions.checkArgument(StringUtils.isNotEmpty(declaration.getBillNo()),new ServiceException("提运单号不能为空"));
        //判断是否生成公路舱单
        TsfPreconditions.checkArgument(Objects.isNull(declaration.getManifestId()),new ServiceException("报关单已经生成舱单，请先删除舱单再操作"));

        List<DeclarationMemberVO> declarationMemberVOS = declarationMemberService.findByDeclarationId(id);
        Long manifestId = declaration.getId();

        Declaration update = new Declaration();
        update.setManifestId(manifestId);
        update.setManifestSwStatus(SingleWindowDeclarationStatusEnum.IMPORTING.getCode());
        declarationMapper.updateByExampleSelective(update,Example.builder(Declaration.class).where(TsfWeekendSqls.<Declaration>custom().andEqualTo(false,Declaration::getId,id)).build());

        DeclarationVO declarationVO = beanMapper.map(declaration,DeclarationVO.class);
        declarationVO.setManifestId(manifestId);
        //境内收发货人
        SubjectVO subject = subjectService.findByCode();
        Optional.ofNullable(subject).orElseThrow(()->new ServiceException("境内收发货人信息不存在"));
        declarationVO.setRcvgdTradeCode(subject.getCustomsCode());
        declarationVO.setRcvgdTradeScc(subject.getSocialNo());
        declarationVO.setRcvgdTradeName(subject.getName());
        //境外收发货人
        declarationVO.setConsignorCode("");
        declarationVO.setConsignorName(declaration.getSubjectOverseasName());
        ManifestPlusVO manifestPlusVO = manifestBiz.generateHighway(declarationVO,declarationMemberVOS);
        DeclareBizSendDTO sendDTO = manifestBiz.sen2SingleWindowHighWay("scm",manifestPlusVO);
        //发送消息通知客户端下载报文
        declareNoticeSender.send(ProducerRabbitConfig.NOTICE_EXCHANGE_NAME,ProducerRabbitConfig.DECLARE_SEND, JSONObject.toJSONString(sendDTO));

        //写入单一窗口日志
        LogSingleWindow logSingleWindow = new LogSingleWindow();
        logSingleWindow.setId(snowflake.nextId());//ID
        logSingleWindow.setDomainId(manifestId.toString());//单据编号
        logSingleWindow.setDomainType(SingleWindowLogEnum.MANIFEST.getCode());
        logSingleWindow.setNode("舱单申请确定发送");
        logSingleWindow.setCreateBy(SecurityUtil.getCurrentPersonName());
        logSingleWindow.setDateCreated(LocalDateTime.now());
        logSingleWindow.setMemo("创建舱单暂存报文信息，发送至单一窗口");
        logSingleWindowService.saveNonNull(logSingleWindow);
    }

    /**
     * 删除公路舱单-进出口通用
     * @param id
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeManifest(Long id) {
        Declaration declaration = super.getById(id);
        Optional.ofNullable(declaration).orElseThrow(()->new ServiceException("报关单不存在"));
        //判断是否为公路运输
        TsfPreconditions.checkArgument(Objects.equals(declaration.getCusTrafModeCode(),"4"),new ServiceException(String.format("报关单%s不是公路运输，不允许操作",declaration.getDocNo())));
        if(StringUtils.isEmpty(declaration.getManifestId())) {
            throw new ServiceException("该报关单还未生成公路舱单，不允许做舱单删除申请");
        }
        Declaration update = new Declaration();
        update.setManifestSwStatus(SingleWindowDeclarationStatusEnum.IMPORTING.getCode());
        declarationMapper.updateByExampleSelective(update,Example.builder(Declaration.class).where(TsfWeekendSqls.<Declaration>custom().andEqualTo(false,Declaration::getId,id)).build());

        List<DeclarationMemberVO> declarationMemberVOS = declarationMemberService.findByDeclarationId(id);
        DeclarationVO declarationVO = beanMapper.map(declaration,DeclarationVO.class);
        ManifestPlusVO manifestPlusVO = manifestBiz.generateHighway(declarationVO,declarationMemberVOS);
        DeclareBizSendDTO sendDTO = manifestBiz.manifestTempRemove("scm",manifestPlusVO);
        //发送消息通知客户端下载报文
        declareNoticeSender.send(ProducerRabbitConfig.NOTICE_EXCHANGE_NAME,ProducerRabbitConfig.DECLARE_SEND, JSONObject.toJSONString(sendDTO));

        //写入单一窗口日志
        LogSingleWindow logSingleWindow = new LogSingleWindow();
        logSingleWindow.setId(snowflake.nextId());//ID
        logSingleWindow.setDomainId(declaration.getManifestId().toString());//单据编号
        logSingleWindow.setDomainType(SingleWindowLogEnum.MANIFEST.getCode());
        logSingleWindow.setNode("舱单删除申请确定发送");
        logSingleWindow.setCreateBy(SecurityUtil.getCurrentPersonName());
        logSingleWindow.setDateCreated(LocalDateTime.now());
        logSingleWindow.setMemo("创建舱单删除申请报文信息，发送至单一窗口");
        logSingleWindowService.saveNonNull(logSingleWindow);
        //允许重复发送删除申请
    }

    @Override
    public Declaration findManifestById(Long manifestId) {
        return declarationMapper.findManifestById(manifestId);
    }

    @Override
    public void updateManifestReceiptData(Long manifestId, String manifestStatusId,String manifestSwStatus) {
        declarationMapper.updateManifestReceiptData(manifestId,manifestStatusId,manifestSwStatus);
    }

    private final static Map<String,Map<String,Object>> fixedMap = new LinkedHashMap<String,Map<String,Object>>(){{
       put("圣禾堂（深圳）电子科技有限公司",new LinkedMap<String,Object>(){{
           // 境内收发货人
           put("rcvgdTradeScc","91440300MA5FX49U3W");
           put("rcvgdTradeCode","4403961C90");
           put("consigneeCode","4777307013");
           put("consigneeCname","圣禾堂（深圳）电子科技有限公司");
           // 境外收发货人
           put("consignorCode","");
           put("consignorEname","SEHOT CO.,LIMITED");
           put("consignorCname","");
           put("consignorAddr","");
           // 消费使用单位
           put("ownerScc","91440300MA5FX49U3W");
           put("ownerCode","4403961C90");
           put("ownerCiqCode","4777307013");
           put("ownerName","圣禾堂（深圳）电子科技有限公司");
       }});
       put("南宁闪电侠供应链有限公司",new LinkedMap<String,Object>(){{
            // 境内收发货人
            put("rcvgdTradeScc","91450100MA5QH9NG4N");
            put("rcvgdTradeCode","4501960AGS");
            put("consigneeCode","4565400347");
            put("consigneeCname","南宁闪电侠供应链有限公司");
            // 境外收发货人
            put("consignorCode","");
            put("consignorEname","SEHOT CO.,LIMITED");
            put("consignorCname","");
            put("consignorAddr","");
            // 消费使用单位
            put("ownerScc","91450100MA5QH9NG4N");
            put("ownerCode","4501960AGS");
            put("ownerCiqCode","4565400347");
            put("ownerName","南宁闪电侠供应链有限公司");
        }});
    }};

    @Override
    public void sendToScmbot(Long id) {
        DeclarationVO declaration = mainDetail(id,null);
        Optional.ofNullable(declaration).orElseThrow(()->new ServiceException("报关单信息不存在"));
        JSONObject sendJson = new JSONObject();
        //组装发送到报关行
        JSONObject declareJson = new JSONObject();
        declareJson.put("customerId",declaration.getCustomerId());
        declareJson.put("cusIEFlag",Objects.equals(declaration.getBillType(),"imp") ? "I" : "E");
        declareJson.put("businessType",Objects.equals(declaration.getBillType(),"imp") ? "imp_ordinary" : "exp_ordinary");
        declareJson.put("customMaster",declaration.getCustomMasterCode());
        declareJson.put("customMasterName",declaration.getCustomMasterName());
        declareJson.put("iePort",declaration.getIePortCode());
        declareJson.put("iePortName",declaration.getIePortName());
        declareJson.put("manualNo",declaration.getRecordNo());
        declareJson.put("contrNo",declaration.getContrNo());
        declareJson.put("ieDate",declaration.getImportDate().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
        // 境内收发货人
        declareJson.put("rcvgdTradeScc",declaration.getRcvgdTradeScc());
        declareJson.put("rcvgdTradeCode",declaration.getRcvgdTradeCode());
        declareJson.put("consigneeCname",declaration.getRcvgdTradeName());
        declareJson.put("consigneeEname","");
        // 境外收发货人
        declareJson.put("consignorCode",declaration.getConsignorCode());
        declareJson.put("consignorEname",declaration.getConsignorName());
        declareJson.put("consignorCname","");
        declareJson.put("consignorAddr","");
        // 消费使用单位
        declareJson.put("ownerScc",declaration.getOwnerScc());
        declareJson.put("ownerCode",declaration.getOwnerCode());
        declareJson.put("ownerName",declaration.getOwnerName());

        String cusTrafModeCode = declaration.getCusTrafModeCode();
        TrafModeCodeEnum trafModeCodeEnum = TrafModeCodeEnum.of(cusTrafModeCode);
        Optional.ofNullable(trafModeCodeEnum).orElseThrow(()->new ServiceException("运输方式错误"));
        declareJson.put("cusTrafMode",cusTrafModeCode);
        declareJson.put("cusTrafModeName",trafModeCodeEnum.getName());

        declareJson.put("cusVoyageNo",declaration.getCusVoyageNo());
        declareJson.put("billNo",declaration.getBillNo());
        declareJson.put("supvModeCdde",declaration.getSupvModeCdde());
        declareJson.put("supvModeCddeName",declaration.getSupvModeCddeName());

        declareJson.put("cutMode",declaration.getCutModeCode());
        declareJson.put("cutModeName",declaration.getCutModeName());

        declareJson.put("licenseNo",declaration.getLicenseNo());

        declareJson.put("cusTradeCountry",declaration.getCusTradeCountryCode());
        declareJson.put("cusTradeCountryName",declaration.getCusTradeCountryName());
        declareJson.put("distinatePort",declaration.getDistinatePortCode());
        declareJson.put("distinatePortName",declaration.getDistinatePortName());

        switch (declaration.getTransactionMode()){
            case "cif":
                declareJson.put("transMode","1");
                declareJson.put("transModeName","CIF");
                break;
            case "fob":
                declareJson.put("transMode","3");
                declareJson.put("transModeName","FOB");
                break;
        }

        declareJson.put("feeMark","");
        declareJson.put("feeMarkName","");
        declareJson.put("feeRate",null);
        declareJson.put("feeCurr","");
        declareJson.put("feeCurrName","");
        String transCostsStr = declaration.getTransCosts();
        if(StringUtils.isNotEmpty(transCostsStr) && transCostsStr.contains("/")) {
            String[] transCosts = transCostsStr.split("/");
            if(new BigDecimal(transCosts[1]).compareTo(BigDecimal.ZERO)==1){
                //运费币制
                String feeCurr = "000".equals(transCosts[0])?"":transCosts[0];
                declareJson.put("feeCurr",feeCurr);
                if(StringUtils.isNotEmpty(feeCurr)) {
                    com.tsfyun.scm.entity.base.Currency currency = currencyService.findById(feeCurr);
                    declareJson.put("feeCurrName",Objects.isNull(currency) ? "" : currency.getName());
                }
                //运费标记
                String feeMark = transCosts[2];
                declareJson.put("feeMark",feeMark);
                if(Objects.equals(feeMark,"1")) {
                    declareJson.put("feeMarkName","率");
                } else if (Objects.equals(feeMark,"2")) {
                    declareJson.put("feeMarkName","单价");
                } else if (Objects.equals(feeMark,"3")) {
                    declareJson.put("feeMarkName","总价");
                }
                //运费/率
                declareJson.put("feeRate",transCosts[1]);
            }
        }

        declareJson.put("insurMark","");
        declareJson.put("insurMarkName","");
        declareJson.put("insurRate",null);
        declareJson.put("insurCurr","");
        declareJson.put("insurCurrName","");

        String insuranceCostsStr = declaration.getInsuranceCosts();
        if(StringUtils.isNotEmpty(insuranceCostsStr) && insuranceCostsStr.contains("/")) {
            String[] insuranceCosts = insuranceCostsStr.split("/");
            if(new BigDecimal(insuranceCosts[1]).compareTo(BigDecimal.ZERO)==1){
                //保费币制
                String insurCurr = "000".equals(insuranceCosts[0])?"":insuranceCosts[0];
                declareJson.put("insurCurr",insurCurr);
                if(StringUtils.isNotEmpty(insurCurr)) {
                    com.tsfyun.scm.entity.base.Currency currency = currencyService.findById(insurCurr);
                    declareJson.put("insurCurrName",Objects.isNull(currency) ? "" : currency.getName());
                }
                //保费标记
                String insurMark = insuranceCosts[2];
                declareJson.put("insurMark",insurMark);
                if(Objects.equals(insurMark,"1")) {
                    declareJson.put("insurMarkName","率");
                } else if (Objects.equals(insurMark,"3")) {
                    declareJson.put("insurMarkName","总价");
                }
                //保费/率
                declareJson.put("insurRate",insuranceCosts[1]);
            }
        }

        declareJson.put("otherMark","");
        declareJson.put("otherMarkName","");
        declareJson.put("otherRate",null);
        declareJson.put("otherCurr","");
        declareJson.put("otherCurrName","");

        String miscCostsStr = declaration.getMiscCosts();
        if(StringUtils.isNotEmpty(miscCostsStr) && miscCostsStr.contains("/")) {
            String[] miscCosts = miscCostsStr.split("/");
            if(new BigDecimal(miscCosts[1]).compareTo(BigDecimal.ZERO)==1){
                //杂费币制
                String otherCurr = "000".equals(miscCosts[0])?"":miscCosts[0];
                declareJson.put("otherCurr",otherCurr);
                if(StringUtils.isNotEmpty(otherCurr)) {
                    Currency currency = currencyService.findById(otherCurr);
                    declareJson.put("otherCurrName",Objects.isNull(currency) ? "" : currency.getName());
                }
                //杂费标记
                String otherMark = miscCosts[2];
                declareJson.put("otherMark",otherMark);
                if(Objects.equals(otherMark,"1")) {
                    declareJson.put("otherMarkName","率");
                } else if (Objects.equals(otherMark,"3")) {
                    declareJson.put("otherMarkName","总价");
                }
                //杂费/率
                declareJson.put("otherRate",miscCosts[1]);
            }
        }

        declareJson.put("packNo",declaration.getPackNo());
        declareJson.put("wrapType",declaration.getWrapTypeCode());
        declareJson.put("wrapTypeName",declaration.getWrapTypeName());
        declareJson.put("grossWt",declaration.getGrossWt());
        declareJson.put("netWt",declaration.getNetWt());

        declareJson.put("cusTradeNationCode",declaration.getCusTradeNationCode());
        declareJson.put("cusTradeNationCodeName",declaration.getCusTradeNationName());
        declareJson.put("ciqEntyPortCode",declaration.getCiqEntyPortCode());
        declareJson.put("ciqEntyPortCodeName",declaration.getCiqEntyPortName());

        declareJson.put("goodsPlace",declaration.getGoodsPlace());
        declareJson.put("despPortCode",declaration.getDestPortCode());
        declareJson.put("despPortCodeName",declaration.getDestPortName());
        declareJson.put("memo",declaration.getMemo());
        declareJson.put("markNo",declaration.getMarkNo());

        //特殊关系确认
        declareJson.put("promiseItem1","0");
        declareJson.put("promise1Name","否");
        declareJson.put("promiseItem2","0");
        declareJson.put("promise2Name","否");
        declareJson.put("promiseItem3","0");
        declareJson.put("promise3Name","否");

        //报关单明细
        JSONArray members = new JSONArray();
        List<DeclarationMemberVO> declarationMemberVOS = memberDetail(id);
        declarationMemberVOS.stream().forEach(memberVO->{
            JSONObject member = new JSONObject();
            member.put("gno",memberVO.getRowNo());
            member.put("contrItem","");
            member.put("codeTs",memberVO.getHsCode());
            member.put("ciqCode","");
            member.put("ciqName","");
            member.put("gname",memberVO.getName());
            member.put("gename","");
            member.put("gspec",memberVO.getDeclareSpec());
            member.put("gqty",memberVO.getQuantity());
            member.put("gunit",memberVO.getUnitCode());
            member.put("gunitName",memberVO.getUnitName());
            member.put("declPrice",memberVO.getUnitPrice());
            member.put("declTotal",memberVO.getTotalPrice());
            member.put("tradeCurr",memberVO.getCurrencyId());
            member.put("tradeCurrName",memberVO.getCurrencyName());
            member.put("qty1",memberVO.getQuantity1());
            member.put("unit1",memberVO.getUnit1Code());
            member.put("unit1Name",memberVO.getUnit1Name());

            //最终目的国
            member.put("destinationCountry",memberVO.getDestinationCountry());
            member.put("destinationCountryName",memberVO.getDestinationCountryName());

            member.put("qty2",memberVO.getQuantity2());
            member.put("unit2",memberVO.getUnit2Code());
            member.put("unit2Name",memberVO.getUnit2Name());

            member.put("dutyMode",memberVO.getDutyMode());
            member.put("dutyModeName",memberVO.getDutyModeName());

            //境内目的地
            member.put("districtCode",memberVO.getDistrictCode());
            member.put("districtCodeName",memberVO.getDistrictName());

            //目的地代码
            member.put("ciqDestCode",memberVO.getCiqDestCode());
            member.put("ciqDestCodeName",memberVO.getCiqDestName());

            //原产国（地区）
            member.put("cusOriginCountry",memberVO.getCountry());
            member.put("cusOriginCountryName",memberVO.getCountryName());

            members.add(member);
        });
        // 固定值
        Map<String,Object> fixedValues = fixedMap.get(declaration.getCustomerName());
        if(CollUtil.isNotEmpty(fixedValues)){
            for(Map.Entry<String,Object> entry : fixedValues.entrySet()){
                declareJson.put(entry.getKey(),entry.getValue());
            }
        }
        sendJson.put("declaration",declareJson);
        sendJson.put("products",members);
        sendJson.put("source","API");

        try {
            log.info("请求内容:" + JSON.toJSONString(sendJson));
            Map<String,String> heads = new LinkedMap<>();
            heads.put("Content-Type","application/json");
            heads.put("salt",salt);

            String result = HttpRequest
                    .post(scmDeclareUrl)
                    .addHeaders(heads)
                    .body(JSONObject.toJSONString(sendJson))
                    .execute()
                    .body();
            log.info("响应内容:" + result);
            Boolean isSuccess = StringUtils.isNotEmpty(result) && Objects.equals(JSONObject.parseObject(result).getString("code"),"1");
            if(isSuccess) {
                log.info("报关单【{}】同步成功",declaration.getDocNo());
            } else {
                if(StringUtils.isNotEmpty(result) && StringUtils.isNotEmpty(JSONObject.parseObject(result).getString("message"))) {
                    throw new ServiceException(JSONObject.parseObject(result).getString("message"));
                } else {
                    throw new ServiceException("发送至异常，请稍后再试");
                }
            }
        }catch (Exception e){
            log.error("报关单数据发送至异常",e);
            if(e instanceof ServiceException) {
                throw new ServiceException(e.getMessage());
            } else {
                throw new ServiceException("发送异常，请稍后再试");
            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void importExcel(MultipartFile file) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        List<ImportDeclarationNoExcel> dataList;
        try {
            dataList = ExcelUtil.readExcel(file, ImportDeclarationNoExcel.class, 1);
        } catch (Exception e) {
            log.error("读取导入的excel文件异常",e);
            throw new ServiceException("导入数据异常，请仔细检查您的导入文件是否符合模板要求");
        }
        TsfPreconditions.checkArgument(CollUtil.isNotEmpty(dataList),new ServiceException("本次没有可以导入的数据"));
        TsfPreconditions.checkArgument(dataList.size() <= uploadMaxmum,new ServiceException(StrUtil.format("您本次导入的数据超过{}条的上限了",uploadMaxmum)));
        //校验参数正确性
        IntStream.range(0,dataList.size()).forEach(idx->{
            ImportDeclarationNoExcel importDeclarationNoExcel = dataList.get(idx);
            int contentRowNo = idx + 1;
            ValidatorUtils.validateEntity(importDeclarationNoExcel,contentRowNo);
        });
        //验证系统单号+报关单号组合是否唯一，不允许存在1个系统单号对应多个报关单号，1个报关单号对应多个系统单号
        Map<String,List<ImportDeclarationNoExcel>> docNoMap = dataList.stream().collect(Collectors.groupingBy(r->r.getDocNo()));
        Map<String,List<ImportDeclarationNoExcel>> declarationNoMap = dataList.stream().collect(Collectors.groupingBy(r->r.getDeclarationNo()));
        List<Declaration> declarations = Lists.newArrayListWithCapacity(dataList.size());
        docNoMap.forEach((docNo,v)->{
            long declarationNos = v.stream().map(ImportDeclarationNoExcel::getDeclarationNo).distinct().count();
            TsfPreconditions.checkArgument(declarationNos == 1,new ServiceException(StrUtil.format("系统单号【{}】对应多个报关单号，请修改后再导入",docNo)));
        });
        declarationNoMap.forEach((declaration,v)->{
            long docNos = v.stream().map(ImportDeclarationNoExcel::getDocNo).distinct().count();
            TsfPreconditions.checkArgument(docNos == 1,new ServiceException(StrUtil.format("报关单号【{}】对应多个系统单号，请修改后再导入",declaration)));
        });
        dataList.stream().forEach(data->{
            Declaration declaration = new Declaration();
            declaration.setDocNo(data.getDocNo());
            declaration.setDeclarationNo(data.getDeclarationNo());
            declarations.add(declaration);
        });
        declarationMapper.updateDeclarationNo(declarations);
        stopwatch.stop();
        log.info("本次成功导入{}条数据，耗时：{}秒",dataList.size(),stopwatch.elapsed(TimeUnit.SECONDS));
    }

    @Override
    public void sendToScmbotDeclare(Long id) {
        DeclarationVO declaration = mainDetail(id,null);
        Optional.ofNullable(declaration).orElseThrow(()->new ServiceException("报关单信息不存在"));
        SubjectVO subject = subjectService.findByCode();
        //暂不进行逻辑校验
        List<OpenApiAccount> list = openApiAccountProperties.getList();
        Map<String,OpenApiAccount> openApiAccountMap = list.stream().collect(Collectors.toMap(OpenApiAccount::getCustomerName, Function.identity()));
        String scmbotCustomerName = subject.getName();
        OpenApiAccount openApiAccount = openApiAccountMap.get(scmbotCustomerName);
        Optional.ofNullable(openApiAccount).orElseThrow(()->new ServiceException(StrUtil.format("客户【{}】未开通API接口推送报关单报关服务",scmbotCustomerName)));
        String customerAccessToken = loginHelper.getScmbotAccessToken(subject.getId(),openApiAccount.getAppKey(),openApiAccount.getAppSecret());
        JSONObject sendJson = new JSONObject();
        //组装发送到
        JSONObject declareJson = new JSONObject();
        declareJson.put("customerId",declaration.getCustomerId());
        declareJson.put("cusIEFlag",Objects.equals(declaration.getBillType(),"imp") ? "I" : "E");
        declareJson.put("businessType",Objects.equals(declaration.getBillType(),"imp") ? "imp_ordinary" : "exp_ordinary");
        declareJson.put("customMaster",declaration.getCustomMasterCode());
        declareJson.put("customMasterName",declaration.getCustomMasterName());
        declareJson.put("iePort",declaration.getIePortCode());
        declareJson.put("iePortName",declaration.getIePortName());
        declareJson.put("manualNo",declaration.getRecordNo());
        declareJson.put("contrNo",declaration.getContrNo());
        declareJson.put("ieDate",declaration.getImportDate().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
        // 境内收发货人
        declareJson.put("rcvgdTradeScc",declaration.getRcvgdTradeScc());
        declareJson.put("rcvgdTradeCode",declaration.getRcvgdTradeCode());
        declareJson.put("consigneeCname",declaration.getRcvgdTradeName());
        declareJson.put("consigneeEname","");
        // 境外收发货人
        declareJson.put("consignorCode",declaration.getConsignorCode());
        declareJson.put("consignorEname",declaration.getConsignorName());
        declareJson.put("consignorCname","");
        declareJson.put("consignorAddr","");
        // 消费使用单位
        declareJson.put("ownerScc",declaration.getOwnerScc());
        declareJson.put("ownerCode",declaration.getOwnerCode());
        declareJson.put("ownerName",declaration.getOwnerName());

        String cusTrafModeCode = declaration.getCusTrafModeCode();
        TrafModeCodeEnum trafModeCodeEnum = TrafModeCodeEnum.of(cusTrafModeCode);
        Optional.ofNullable(trafModeCodeEnum).orElseThrow(()->new ServiceException("运输方式错误"));
        declareJson.put("cusTrafMode",cusTrafModeCode);
        declareJson.put("cusTrafModeName",trafModeCodeEnum.getName());

        declareJson.put("cusVoyageNo",declaration.getCusVoyageNo());
        declareJson.put("billNo",declaration.getBillNo());
        declareJson.put("supvModeCdde",declaration.getSupvModeCdde());
        declareJson.put("supvModeCddeName",declaration.getSupvModeCddeName());

        declareJson.put("cutMode",declaration.getCutModeCode());
        declareJson.put("cutModeName",declaration.getCutModeName());

        declareJson.put("licenseNo",declaration.getLicenseNo());

        declareJson.put("cusTradeCountry",declaration.getCusTradeCountryCode());
        declareJson.put("cusTradeCountryName",declaration.getCusTradeCountryName());
        declareJson.put("distinatePort",declaration.getDistinatePortCode());
        declareJson.put("distinatePortName",declaration.getDistinatePortName());

        switch (declaration.getTransactionMode()){
            case "cif":
                declareJson.put("transMode","1");
                declareJson.put("transModeName","CIF");
                break;
            case "fob":
                declareJson.put("transMode","3");
                declareJson.put("transModeName","FOB");
                break;
        }

        declareJson.put("feeMark","");
        declareJson.put("feeMarkName","");
        declareJson.put("feeRate",null);
        declareJson.put("feeCurr","");
        declareJson.put("feeCurrName","");
        String transCostsStr = declaration.getTransCosts();
        if(StringUtils.isNotEmpty(transCostsStr) && transCostsStr.contains("/")) {
            String[] transCosts = transCostsStr.split("/");
            if(new BigDecimal(transCosts[1]).compareTo(BigDecimal.ZERO)==1){
                //运费币制
                String feeCurr = "000".equals(transCosts[0])?"":transCosts[0];
                declareJson.put("feeCurr",feeCurr);
                if(StringUtils.isNotEmpty(feeCurr)) {
                    com.tsfyun.scm.entity.base.Currency currency = currencyService.findById(feeCurr);
                    declareJson.put("feeCurrName",Objects.isNull(currency) ? "" : currency.getName());
                }
                //运费标记
                String feeMark = transCosts[2];
                declareJson.put("feeMark",feeMark);
                if(Objects.equals(feeMark,"1")) {
                    declareJson.put("feeMarkName","率");
                } else if (Objects.equals(feeMark,"2")) {
                    declareJson.put("feeMarkName","单价");
                } else if (Objects.equals(feeMark,"3")) {
                    declareJson.put("feeMarkName","总价");
                }
                //运费/率
                declareJson.put("feeRate",transCosts[1]);
            }
        }

        declareJson.put("insurMark","");
        declareJson.put("insurMarkName","");
        declareJson.put("insurRate",null);
        declareJson.put("insurCurr","");
        declareJson.put("insurCurrName","");

        String insuranceCostsStr = declaration.getInsuranceCosts();
        if(StringUtils.isNotEmpty(insuranceCostsStr) && insuranceCostsStr.contains("/")) {
            String[] insuranceCosts = insuranceCostsStr.split("/");
            if(new BigDecimal(insuranceCosts[1]).compareTo(BigDecimal.ZERO)==1){
                //保费币制
                String insurCurr = "000".equals(insuranceCosts[0])?"":insuranceCosts[0];
                declareJson.put("insurCurr",insurCurr);
                if(StringUtils.isNotEmpty(insurCurr)) {
                    com.tsfyun.scm.entity.base.Currency currency = currencyService.findById(insurCurr);
                    declareJson.put("insurCurrName",Objects.isNull(currency) ? "" : currency.getName());
                }
                //保费标记
                String insurMark = insuranceCosts[2];
                declareJson.put("insurMark",insurMark);
                if(Objects.equals(insurMark,"1")) {
                    declareJson.put("insurMarkName","率");
                } else if (Objects.equals(insurMark,"3")) {
                    declareJson.put("insurMarkName","总价");
                }
                //保费/率
                declareJson.put("insurRate",insuranceCosts[1]);
            }
        }

        declareJson.put("otherMark","");
        declareJson.put("otherMarkName","");
        declareJson.put("otherRate",null);
        declareJson.put("otherCurr","");
        declareJson.put("otherCurrName","");

        String miscCostsStr = declaration.getMiscCosts();
        if(StringUtils.isNotEmpty(miscCostsStr) && miscCostsStr.contains("/")) {
            String[] miscCosts = miscCostsStr.split("/");
            if(new BigDecimal(miscCosts[1]).compareTo(BigDecimal.ZERO)==1){
                //杂费币制
                String otherCurr = "000".equals(miscCosts[0])?"":miscCosts[0];
                declareJson.put("otherCurr",otherCurr);
                if(StringUtils.isNotEmpty(otherCurr)) {
                    Currency currency = currencyService.findById(otherCurr);
                    declareJson.put("otherCurrName",Objects.isNull(currency) ? "" : currency.getName());
                }
                //杂费标记
                String otherMark = miscCosts[2];
                declareJson.put("otherMark",otherMark);
                if(Objects.equals(otherMark,"1")) {
                    declareJson.put("otherMarkName","率");
                } else if (Objects.equals(otherMark,"3")) {
                    declareJson.put("otherMarkName","总价");
                }
                //杂费/率
                declareJson.put("otherRate",miscCosts[1]);
            }
        }

        declareJson.put("packNo",declaration.getPackNo());
        declareJson.put("wrapType",declaration.getWrapTypeCode());
        declareJson.put("wrapTypeName",declaration.getWrapTypeName());
        declareJson.put("grossWt",declaration.getGrossWt());
        declareJson.put("netWt",declaration.getNetWt());

        declareJson.put("cusTradeNationCode",declaration.getCusTradeNationCode());
        declareJson.put("cusTradeNationCodeName",declaration.getCusTradeNationName());
        declareJson.put("ciqEntyPortCode",declaration.getCiqEntyPortCode());
        declareJson.put("ciqEntyPortCodeName",declaration.getCiqEntyPortName());

        declareJson.put("goodsPlace",declaration.getGoodsPlace());
        declareJson.put("despPortCode",declaration.getDestPortCode());
        declareJson.put("despPortCodeName",declaration.getDestPortName());
        declareJson.put("memo",declaration.getMemo());
        declareJson.put("markNo",declaration.getMarkNo());

        //特殊关系确认
        declareJson.put("promiseItem1","0");
        declareJson.put("promise1Name","否");
        declareJson.put("promiseItem2","0");
        declareJson.put("promise2Name","否");
        declareJson.put("promiseItem3","0");
        declareJson.put("promise3Name","否");

        //报关单明细
        JSONArray members = new JSONArray();
        List<DeclarationMemberVO> declarationMemberVOS = memberDetail(id);
        declarationMemberVOS.stream().forEach(memberVO->{
            JSONObject member = new JSONObject();
            member.put("gno",memberVO.getRowNo());
            member.put("contrItem","");
            member.put("codeTs",memberVO.getHsCode());
            member.put("ciqCode","");
            member.put("ciqName","");
            member.put("gname",memberVO.getName());
            member.put("gename","");
            member.put("gspec",memberVO.getDeclareSpec());
            member.put("gqty",memberVO.getQuantity());
            member.put("gunit",memberVO.getUnitCode());
            member.put("gunitName",memberVO.getUnitName());
            member.put("declPrice",memberVO.getUnitPrice());
            member.put("declTotal",memberVO.getTotalPrice());
            member.put("tradeCurr",memberVO.getCurrencyId());
            member.put("tradeCurrName",memberVO.getCurrencyName());
            member.put("qty1",memberVO.getQuantity1());
            member.put("unit1",memberVO.getUnit1Code());
            member.put("unit1Name",memberVO.getUnit1Name());

            //最终目的国
            member.put("destinationCountry",memberVO.getDestinationCountry());
            member.put("destinationCountryName",memberVO.getDestinationCountryName());

            member.put("qty2",memberVO.getQuantity2());
            member.put("unit2",memberVO.getUnit2Code());
            member.put("unit2Name",memberVO.getUnit2Name());

            member.put("dutyMode",memberVO.getDutyMode());
            member.put("dutyModeName",memberVO.getDutyModeName());

            //境内目的地
            member.put("districtCode",memberVO.getDistrictCode());
            member.put("districtCodeName",memberVO.getDistrictName());

            //目的地代码
            member.put("ciqDestCode",memberVO.getCiqDestCode());
            member.put("ciqDestCodeName",memberVO.getCiqDestName());

            //原产国（地区）
            member.put("cusOriginCountry",memberVO.getCountry());
            member.put("cusOriginCountryName",memberVO.getCountryName());

            members.add(member);
        });
        // 固定值
        Map<String,Object> fixedValues = fixedMap.get(declaration.getCustomerName());
        if(CollUtil.isNotEmpty(fixedValues)){
            for(Map.Entry<String,Object> entry : fixedValues.entrySet()){
                declareJson.put(entry.getKey(),entry.getValue());
            }
        }
        sendJson.put("declaration",declareJson);
        sendJson.put("products",members);
        sendJson.put("source","API");
        sendJson.put("outNo",declaration.getDocNo());
        log.info("准备调用报关，请求【{}】",JSONObject.toJSONString(sendJson));
        try {
            String declareResponse = HttpRequest.post(sendToScmbotUrl)
                    .header("accessToken", customerAccessToken).body(JSONObject.toJSONString(sendJson))
                    .timeout(5000).execute().body();
            log.info("调用报关，响应【{}】", declareResponse);
            TsfPreconditions.checkArgument(StringUtils.isNotEmpty(declareResponse), new ServiceException("系统响应异常，请稍后再试"));
            Result result = JSONObject.parseObject(declareResponse, Result.class);
            if (!result.isSuccess()) {
                throw new ServiceException(result.getMessage());
            }
        } catch (Exception e){
            log.error("报关单数据发送至异常",e);
            if(e instanceof ServiceException) {
                throw new ServiceException(e.getMessage());
            } else {
                throw new ServiceException("发送异常，请稍后再试");
            }
        }
    }

    @Override
    public Map<String,Object> exportDraft(Long id,List<String> notShowFields) {
        Map<String,Object> dataMap = Maps.newHashMap();
        Declaration declaration = super.getById(id);
        log.info(JSONObject.toJSONString(declaration));
        Optional.ofNullable(declaration).orElseThrow(()->new ServiceException("报关单不存在"));
        //获取报关单明细
        List<DeclarationMemberVO> members = declarationMemberService.findByDeclarationId(id);
        log.info(JSONObject.toJSONString(members));
        Subject subject = subjectService.getById(declaration.getSubjectId());
        HSSFWorkbook workbook = ExportUtil.exportDeclareDraft(declaration,members,subject,declaration.getSubjectOverseasName(),notShowFields);
        dataMap.put("declaration",declaration);
        dataMap.put("workbook",workbook);
        return dataMap;
    }

    @Override
    public ContractPlusVO overseasContractData(Long id) {
        ContractPlusVO contractPlusVO = new ContractPlusVO();
        ContractVO contractVO = declarationMapper.contractData(id);
        TsfPreconditions.checkArgument(Objects.nonNull(contractVO),new ServiceException("合同数据不存在"));
        BillTypeEnum billTypeEnum = BillTypeEnum.of(contractVO.getBillType());
        //设置卖方信息
        contractVO.setSellerNameEn(contractVO.getSubjectOverseasName());
        BuyerSellerInfo buyerSellerInfo = subjectBiz.getBySubjectOverseasType(billTypeEnum,contractVO.getSubjectOverseasId(),contractVO.getSubjectOverseasType());
        contractVO.setSellerName(buyerSellerInfo.getName());
        contractVO.setSellerAddress(buyerSellerInfo.getAddress());
        contractVO.setSellerAddressEn(buyerSellerInfo.getAddressEn());
        contractVO.setSellerTel(buyerSellerInfo.getTel());
        contractVO.setSellerFax(buyerSellerInfo.getFax());
        contractVO.setSellerChapter(buyerSellerInfo.getChapter());
        if(StringUtils.isEmpty(contractVO.getSellerNameEn())) {
            contractVO.setSellerNameEn(buyerSellerInfo.getNameEn());
        }
        if(Objects.equals(BillTypeEnum.of(contractVO.getBillType()),BillTypeEnum.EXP)){
            // 出口买卖双方反过来
            ContractVO contract = beanMapper.map(contractVO,ContractVO.class);
            contractVO.setBuyerName("");
            contractVO.setBuyerNameEn(contract.getSupplierName());
            contractVO.setBuyerAddress("");
            contractVO.setBuyerAddressEn(contract.getSupplierAddress());
            contractVO.setBuyerTel("");
            contractVO.setBuyerFax("");
            contractVO.setBuyerChapter("");

            contractVO.setSellerName(contract.getSellerName());
            contractVO.setSellerNameEn(contract.getSellerNameEn());
            contractVO.setSellerAddress(contract.getSellerAddress());
            contractVO.setSellerAddressEn(contract.getSellerAddressEn());
            contractVO.setSellerTel(contract.getSellerTel());
            contractVO.setSellerFax(contract.getSellerFax());
            contractVO.setSellerChapter(contract.getSellerChapter());

            // 合同日期 出口日期向前推45天
            contractVO.setContractDate(contractVO.getImportDate().plusDays(-45));
            // 装运期限 出口日期向前推15天，2022-01-05宝珊提出再往后推1个半月
            contractVO.setShipmentDate(contractVO.getImportDate().plusDays(-60));
            // 箱单发票日期 出口日期向前推1天
            contractVO.setInvoiceDate(contractVO.getImportDate().plusDays(-1));
        }else{
            // 合同日期 进口日期向前推5天
            contractVO.setContractDate(contractVO.getImportDate().plusDays(-5));
            // 装运期限 进口日期向后推25天
            contractVO.setShipmentDate(contractVO.getImportDate().plusDays(25));
            // 箱单发票日期 = 进口日期
            contractVO.setInvoiceDate(contractVO.getImportDate());
        }
        List<DeclarationMemberVO> memberVOS = memberDetail(id);
        contractPlusVO.setMain(contractVO);
        contractPlusVO.setMembers(beanMapper.mapAsList(memberVOS,ContractMemberVO.class));
        return contractPlusVO;
    }

    @Override
    public void declarationCompletedCallBack(String docNo) {
        log.info("接收到申报完成回调，系统单号【{}】",docNo);
        Declaration declaration = findByDocNo(docNo);
        ValidatorUtils.isTrue(Objects.nonNull(declaration),()->new ServiceException("报关单号不存在"));
        DeclarationStatusEnum declarationStatusEnum = DeclarationStatusEnum.of(declaration.getStatusId());
        ValidatorUtils.isTrue(Objects.equals(declarationStatusEnum,DeclarationStatusEnum.IN_DECLARE),()->new ServiceException("报关单状态不正确"));

        TaskDTO taskDTO = new TaskDTO();
        taskDTO.setNewStatusCode(DeclarationStatusEnum.COMPLETE.getCode());
        taskDTO.setDocumentId(declaration.getId().toString());
        taskDTO.setDocumentClass(Declaration.class.getSimpleName());
        taskDTO.setOperation(DomainOprationEnum.DECLARATION_COMPLETED.getCode());
        taskDTO.setOperator("系统");
        taskDTO.setMemo("系统回调");
        declarationCompleted(taskDTO);
    }

}
